// $URL: http://mingw-lib.googlecode.com/svn/trunk/working/avcodec_to_widget_9/t_box_player_ctx.cpp $
// $Rev: 320 $
// $Author: akio.miyoshi $
// $Date:: 2010-07-31 10:13:06 +0000#$

#include "t_box_player_ctx.h"
#include "t_box_input.h"

#if 0x0
//http://blog.csdn.net/huyinguo/archive/2009/10/25/4725326.aspx
//needs ffmpeg-mt
static void validate_thread_parameters(AVCodecContext *avctx)
{
    int frame_threading_supported = (avctx->codec->capabilities & CODEC_CAP_FRAME_THREADS)
                                    && !(avctx->flags & CODEC_FLAG_TRUNCATED)
                                    && !(avctx->flags & CODEC_FLAG_LOW_DELAY)
                                    && !(avctx->flags2 & CODEC_FLAG2_CHUNKS);
    if (avctx->thread_count <= 1)
        avctx->active_thread_type = 0;
    else if (frame_threading_supported && (avctx->thread_type & FF_THREAD_FRAME))
        avctx->active_thread_type = FF_THREAD_FRAME;
    else
        avctx->active_thread_type = FF_THREAD_SLICE;
}
#endif

T_Box_Stream_Info::T_Box_Stream_Info(T_Box_Player_Context *a_player_ctx, AVMediaType a_media_type)
    : isValid(false)
    , stream_index(-1)
    , codec_ctx(0)
    , codec(0)
    , stream(0)
    , packet_queue(a_player_ctx)
{
    Q_ASSERT(a_player_ctx->m_format_ctx);
    Q_ASSERT(a_media_type==CODEC_TYPE_AUDIO || a_media_type==CODEC_TYPE_VIDEO);
    QString v_media_type_str = a_media_type==CODEC_TYPE_AUDIO ? "audio" : "video";
    this->stream_index=-1;
    for(unsigned int i=0; i<a_player_ctx->m_format_ctx->nb_streams; i++)
        if(a_player_ctx->m_format_ctx->streams[i]->codec->codec_type==a_media_type)
        {
            this->stream_index=i;
            break;
        }
    if(this->stream_index<0)
    {
        qDebug() << QString("No %1 stream.").arg(v_media_type_str);
        this->isValid = false;
    }
    else
    {
        qDebug()<< QString("m_%1.stream.stream_index]").arg(v_media_type_str)
                << this->stream_index;
        this->isValid = true;
        this->stream = a_player_ctx->m_format_ctx->streams[this->stream_index];
        this->codec_ctx=this->stream->codec;
        if(a_media_type==CODEC_TYPE_AUDIO)
        {
            if (this->codec_ctx->channels > 0)
                this->codec_ctx->request_channels = FFMIN(2, this->codec_ctx->channels);
            else
                this->codec_ctx->request_channels = 2;
        }
        this->codec=avcodec_find_decoder(this->codec_ctx->codec_id);
        if(!this->codec)
        {
            qDebug() << QString("Unsupported %1 codec.").arg(v_media_type_str);
            this->isValid = false;
        }
#if 0x0
        else  if(avcodec_open(this->codec_ctx, this->codec)<0)
        {
            qDebug() << QString("avcodec_open (%1) failed.").arg(v_media_type_str);
            this->isValid = false;
        }
#else
        else
        {
#if 0x0
            if(a_media_type==CODEC_TYPE_VIDEO)
            {
                this->codec_ctx->active_thread_type = FF_THREAD_FRAME;
                //this->codec_ctx->active_thread_type = FF_THREAD_SLICE;
                avcodec_thread_init(this->codec_ctx, 8);
                this->codec_ctx->active_thread_type = FF_THREAD_FRAME;
                ////this->codec_ctx->thread_count = 8;
                ////this->codec_ctx->thread_type = FF_THREAD_SLICE;
            }
#endif
            if(avcodec_open(this->codec_ctx, this->codec)<0)
            {
                qDebug() << QString("avcodec_open (%1) failed.").arg(v_media_type_str);
                this->isValid = false;
            }
#if 0x0
            if(a_media_type==CODEC_TYPE_VIDEO)
            {
                validate_thread_parameters(this->codec_ctx);
            }
#endif
        }
#endif
    }
}

T_Box_Player_Context::T_Box_Player_Context(T_Box_Input *a_box_input, AVFormatContext *a_format_ctx)
    : m_box_input(a_box_input)
    , m_format_ctx(a_format_ctx)
    , m_audio(this, CODEC_TYPE_AUDIO)
    , m_video(this, CODEC_TYPE_VIDEO)
    , m_audioOutput(NULL)
    ////, m_frame_queue(this)
    , m_packet_thread(0)
    , m_decode_thread(0)
    , m_resize_pic(PIX_FMT_BGRA, 10, 10)
{
    Q_ASSERT(m_box_input);
    Q_ASSERT(m_format_ctx);
    qDebug() << "[m_video.isValid]" << m_video.isValid;
    qDebug() << "[m_audio.isValid]" << m_audio.isValid;
}

T_Box_Player_Context::~T_Box_Player_Context()
{
    qDebug() << "[~T_Box_Player_Context(1)]";
    if(m_audioOutput) m_audioOutput->stop();
    qDebug() << "[~T_Box_Player_Context(2)]";
    if(m_audio.codec_ctx) avcodec_close(m_audio.codec_ctx);
    qDebug() << "[~T_Box_Player_Context(3)]";
    if(m_video.codec_ctx) avcodec_close(m_video.codec_ctx);
    qDebug() << "[~T_Box_Player_Context(4)]";
    //av_close_input_file(m_format_ctx);
    Q_ASSERT(m_format_ctx);
    ////if(m_format_ctx)
    {
        qDebug() << "[~T_Box_Player_Context(4.1)]";
        m_box_input->close();
        qDebug() << "[~T_Box_Player_Context(4.2)]";
#warning before av_close_input_file() stop <packet reader thread> & <video decode/render thread>
#if 0x0
        AVFormatContext *v_format_ctx = m_format_ctx;
        qDebug() << "[~T_Box_Player_Context(4.3)]";
        m_format_ctx = NULL;
        qDebug() << "[~T_Box_Player_Context(4.4)]";
#endif
        //av_close_input_file(v_format_ctx);
        qDebug() << "[~T_Box_Player_Context(4.5)]";
    }
    qDebug() << "[~T_Box_Player_Context(5)]";
}

T_AV_Packet *T_Box_Player_Context::readPacket()
{
    AVPacket v_av_packet;
    if(::av_read_frame(m_format_ctx, &v_av_packet)<0)
    {
        return NULL;
    }
    T_AV_Packet *v_obj_packet = new T_AV_Packet(this, &v_av_packet);
    return v_obj_packet;
}

T_AV_Packet *T_Box_Player_Context::enqueuePacket()
{
    T_AV_Packet *v_av_packet;
    while((v_av_packet = this->readPacket()))
    {
        if(v_av_packet->m_stream_index==this->m_video.stream_index)
        {
            this->m_video.packet_queue.enqueue(v_av_packet);
            return v_av_packet;
        }
        else if(v_av_packet->m_stream_index==this->m_audio.stream_index)
        {
            this->m_audio.packet_queue.enqueue(v_av_packet);
            return v_av_packet;
        }
        else
        {
            delete v_av_packet;
        }
    }
    if(!this->m_audio.packet_queue.isComplete())
    {
        this->m_audio.packet_queue.enqueue(NULL);
    }
    if(!this->m_video.packet_queue.isComplete())
    {
        this->m_video.packet_queue.enqueue(NULL);
    }
    return NULL;
}

bool T_Box_Player_Context::handleSeek()
{
    T_Box_Seek_Manager *v_seek_manager = this->seekManager();
    if(v_seek_manager->pendingRequest() && v_seek_manager->elapsed()>100)
    {
        int v_seek_target = v_seek_manager->dequeueRequest();
        if(v_seek_target>v_seek_manager->maxTiming())
        {
            v_seek_target = v_seek_manager->maxTiming();
        }
        this->seek(v_seek_target);
        v_seek_manager->reset();
    }
    if(this->needBuffering())
    {
        return false; //must read next packet.
    }
    else
    {
        int v_elapsed = v_seek_manager->elapsed();
        int v_critical = 700;
        if(v_elapsed < v_critical)
        {
            //v_seek_manager->waitCondition()->wait(v_critical - v_elapsed); //N.G.: may become needBuffering() soon
            v_seek_manager->wait(1); //may become needBuffering() soon; so that looping is needed.
            return true; //request looping
        }
        else
        {
            return false; //may read next packet.
        }
    }
    //Q_ASSERT(0);
}

qint64 T_Box_Player_Context::seek(qint64 a_msec)
{
    //qDebug() << "[T_Box_Player_Context::seek()]" << a_msec;
    QWriteLocker v_seek_locker(this->seekManager());
    Q_UNUSED(v_seek_locker);
    int ret = avformat_seek_file(
            this->m_format_ctx,
            -1,
            LONG_LONG_MIN,
            a_msec * AV_TIME_BASE / 1000,
            LONG_LONG_MAX,
            0);
    Q_UNUSED(ret);
    if(ret<0)
    {
        return -1;
    }
    this->m_generator->breakTimer();
    this->m_audio.packet_queue.clear();
    this->m_video.packet_queue.clear();
    ////this->m_frame_queue.clear();
    // set default timing
    qint64 v_result = a_msec;
    this->m_stopwatch.setElapsed(v_result);
    // take first packet timing
    if(T_AV_Packet *v_av_packet = this->enqueuePacket())
    {
        v_result = v_av_packet->timing();
        this->m_stopwatch.setElapsed(v_result);
    }
    ////qDebug() << a_msec << v_result << v_result-a_msec;
    return v_result;
}

void T_Box_Player_Packet_Thread::run()
{
    T_Box_Seek_Manager *v_seek_manager = m_player_ctx->seekManager();
    for(;;)
    {
        if(this->closing())
        {
            break;
        }
        if(m_player_ctx->handleSeek())
        {
            continue;
        }
        T_AV_Packet *v_av_packet = m_player_ctx->enqueuePacket();
        if(!v_av_packet)
        {
            v_seek_manager->registerTiming(LONG_LONG_MAX);
        }
        else
        {
            v_seek_manager->registerTiming(v_av_packet->timing());
        }
        if(!v_av_packet)
        {
            v_seek_manager->wait(1);
        }
    }
}

void T_Box_Player_Decode_Thread::run()
{
#if !USE_DECODE_THREAD
    if(true)
    {
        return;
    }
#endif
    //T_Box_Seek_Manager *v_seek_manager = m_player_ctx->seekManager();
    T_AV_Packet_Queue *v_video_qu = &m_player_ctx->m_video.packet_queue;
    for(;;)
    {
        msleep(1);
        if(this->closing())
        {
            break;
        }
        //v_video_qu->lockForWrite();
        for(int i=0; i<v_video_qu->size(); i++)
        {
            if(i>=T_Box::PLAYER_IMAGE_STOCK)
            {
                break;
            }
            QSharedPointer<T_AV_Packet> v_packet = v_video_qu->value(i);
            if(v_packet==NULL) break;
            if(!v_packet->isDecoded())
            {
                v_packet->decodeVideoToNull();
                break;
            }
        }
        //v_video_qu->unlock();
    }
}
